#include "dog_mem.h"
#include "string.h"

#ifndef DOS_ALIGN_SIZE
#define DOS_ALIGN_SIZE (4)
#endif

// #ifndef DOS_MEM_HEAP_SIZE
// #define DOS_MEM_HEAP_SIZE 1024 * 10
// #endif

// static d_uint8_t _mem_heap[DOS_MEM_HEAP_SIZE];

static d_uint8_t *_align_mem_heap_begin = D_NULL;
static d_uint8_t *_align_mem_heap_end = D_NULL;

static thread_safe_t thread_safe;

/**
 * get memory list next memory node
 */
static dog_mem_heap_node_t *_dog_get_next_node(dog_mem_heap_node_t *node)
{
    dog_mem_heap_info_t *mem_heap_info = (dog_mem_heap_info_t *)_align_mem_heap_begin;

    if (mem_heap_info->mem_tail == node)
        return D_NULL;
    else
        return (dog_mem_heap_node_t *)(node->user_mem + node->mem_node_size);
}

/**
 * @brief       alloc memory
 * @param[in]   size The amount of memory you want to
 * @return      NULL, alloc failed; if not NULL, alloc success
 * @author    
 * @date        2021-11-20
 * @version     v1.0
 * @note        alloc system mem , reference from LiteOS
 */
void *dog_mem_alloc(d_uint32_t size)
{
    dog_mem_heap_info_t *mem_heap_info = (dog_mem_heap_info_t *)D_NULL;
    dog_mem_heap_node_t *mem_node = (dog_mem_heap_node_t *)D_NULL;
    dog_mem_heap_node_t *best_node = (dog_mem_heap_node_t *)D_NULL;
    dog_mem_heap_node_t *node = (dog_mem_heap_node_t *)D_NULL;

    void *result;

    /* lock */
    if (thread_safe.lock)
        thread_safe.lock();

    mem_heap_info = (dog_mem_heap_info_t *)_align_mem_heap_begin;
    if (D_NULL == mem_heap_info)
    {
        return D_NULL;
    }

    size = DOG_ALIGN(size, DOS_ALIGN_SIZE); /* align */

    mem_node = mem_heap_info->mem_tail; /** get mem node */

    while (mem_node)
    { /** When a memory node exists */
        /**If current memory is not in use and the memory node mem_node_size > size, and best memory node  mem_node_size > current memory node mem_node_size */
        if ((!mem_node->mem_used) && (mem_node->mem_node_size > size) && (!best_node || best_node->mem_node_size > mem_node->mem_node_size))
        {
            best_node = mem_node; /** best memory node is current memory node */
            if (best_node->mem_node_size == size)
            {
                goto FIND_BEST_MEM; /** The best memory node is exactly equal to the memory block size the user needs */
            }
        }
        mem_node = mem_node->prev; /** traversing the memory list */
    }

    if (!best_node)
    { /*alloc failed*/
        printf("there's not enough whole to alloc %d Bytes!\n", size);

        /* unlock */
        if (thread_safe.unlock)
            thread_safe.unlock();

        return D_NULL;
    }

    /* Can cut the memory node */
    if (best_node->mem_node_size - size > MEM_NODE_SIZE)
    {
        mem_node = (dog_mem_heap_node_t *)(best_node->user_mem + size); /** get a new free memory node address */
        mem_node->mem_used = 0;                                                    /** free memory node mem_used flag is set to 0 */
        mem_node->mem_node_size = best_node->mem_node_size - size - MEM_NODE_SIZE; /** calculate the free memory node size mem_node_size  */
        mem_node->prev = best_node;                                                /** insert free memory block list */

        if (best_node != mem_heap_info->mem_tail)
        {
            if (D_NULL != (node = _dog_get_next_node(mem_node))) /** get memory list next node */
                node->prev = mem_node;                           /** insert free memory block list */
        }
        else
            mem_heap_info->mem_tail = mem_node; /** set memory heap struct info mem_tail pointer pointing new free memory node*/
    }

FIND_BEST_MEM:                       /** find best memory node */
    best_node->mem_align = 0;        /** reserved */
    best_node->mem_used = 1;         /** memory node is already in use */
    best_node->mem_node_size = size; /** set memory node info */
    result = best_node->user_mem;

    mem_heap_info->mem_heap_size -= size;

    /* unlock */
    if (thread_safe.unlock)
        thread_safe.unlock();

    return result;
}

/**
 * @brief       Free memory
 * @param[in]   dos_mem
 * @param[out]  NULL
 * @return      reference 'enum mem_error'
 * @author     
 * @date        2021-11-20
 * @version     v1.0
 * @note        free mem , reference from LiteOS
 */

d_err_t dog_mem_free(void *mem)
{
    dog_mem_heap_info_t *mem_heap_info = (dog_mem_heap_info_t *)D_NULL;
    dog_mem_heap_node_t *free_node = (dog_mem_heap_node_t *)D_NULL;
    dog_mem_heap_node_t *node = (dog_mem_heap_node_t *)D_NULL;

    if (mem == D_NULL)
    {
        printf("mem is null!\n");
        return D_NOK;
    }

    mem_heap_info = (dog_mem_heap_info_t *)_align_mem_heap_begin; /** get the memory management structure from the memory heap start address */
    if (D_NULL == mem_heap_info)
    {
        printf("mem info struct is null!\n");
        return D_NOK;
    }

    /** Find the real address of the memory node by offset */
    free_node = (dog_mem_heap_node_t *)((d_uint32_t)mem - MEM_NODE_SIZE);

    if (0 == free_node->mem_used)
    { /** determine if the memory node is already in use */
        printf("mem is unused\n");
        return D_NOK;
    }

    free_node->mem_used = 0; /** can free memory node , mem_used flag is set to 0 */

    mem_heap_info->mem_heap_size += free_node->mem_node_size;

    while ((D_NULL != free_node->prev) && (0 == free_node->prev->mem_used))
    {
        free_node = free_node->prev; /** if current memory node prev is not null, and the node is free, can merging */
    }

    while (((node = (dog_mem_heap_node_t *)_dog_get_next_node(free_node)) != D_NULL) && (0 == node->mem_used))
    {
        free_node->mem_node_size += MEM_NODE_SIZE + node->mem_node_size; /** reset the mem_node_size of the new memory node */

        if (node == mem_heap_info->mem_tail)
            mem_heap_info->mem_tail = free_node; /** set mem_tail pointer to the memory management information structure */
    }

    if ((node = (dog_mem_heap_node_t *)_dog_get_next_node(free_node)) != D_NULL)
        node->prev = free_node; /** insert free memory block list */

    return D_OK;
}

/**
 * @brief       
 * @param[in]   NULL
 * @param[out]  NULL
 * @return      bool
 * @date        2021-11-20
 * @version     v1.0
 * @note        This is a function that internally initializes the memory heap, only for internal calls.
 */
d_bool dog_mem_heap_init(d_uint8_t *mem_start_addr, d_uint32_t mem_size)
{
    dog_mem_heap_info_t *mem_heap_info = (dog_mem_heap_info_t *)D_NULL;
    dog_mem_heap_node_t *memheap_node = (dog_mem_heap_node_t *)D_NULL;

    d_uint32_t align_memheap_size;

    /* Get the begin and end addresses of the memory heap */
    d_uint32_t memheap_addr = (d_uint32_t)mem_start_addr;
    _align_mem_heap_begin = (d_uint8_t *)DOG_ALIGN(memheap_addr, DOS_ALIGN_SIZE);

    _align_mem_heap_end = (d_uint8_t *)memheap_addr + mem_size;
    _align_mem_heap_end = (d_uint8_t *)DOG_ALIGN_DOWN((d_uint32_t)_align_mem_heap_end, DOS_ALIGN_SIZE);

    /* Get the actual size of the memory heap */
    align_memheap_size = (d_uint32_t)(_align_mem_heap_end - _align_mem_heap_begin);

    /* Memory heap management information control block */
    mem_heap_info = (dog_mem_heap_info_t *)_align_mem_heap_begin;

    if ((!mem_heap_info) || (align_memheap_size <= (MEM_INFO_SIZE + MEM_NODE_SIZE)))
        return D_FALSE;

    memset(_align_mem_heap_begin, 0, align_memheap_size);

    /* Init  */
    mem_heap_info->mem_heap_addr = _align_mem_heap_begin;
    mem_heap_info->mem_heap_size = align_memheap_size - MEM_INFO_SIZE - MEM_NODE_SIZE;
    mem_heap_info->MemHead = (dog_mem_heap_node_t *)(_align_mem_heap_begin + MEM_INFO_SIZE);
    memheap_node = mem_heap_info->MemHead;
    mem_heap_info->mem_tail = memheap_node;

    /* Init memory node information control block */
    memheap_node->mem_used = 0;
    memheap_node->mem_node_size = mem_heap_info->mem_heap_size;
    memheap_node->prev = D_NULL;

    return D_TRUE;
}

d_bool dog_mem_lock_init(void (*lock)(void), void (*unlock)(void))
{
    thread_safe.lock = lock;
    thread_safe.unlock = unlock;
    return D_TRUE;
}

/**
 * get the remaining memory information of the current system
 */
d_uint32_t dog_get_mem_heap_info(void)
{
    dog_mem_heap_info_t *mem_heap_info = (dog_mem_heap_info_t *)D_NULL;

    /* Memory heap management information control block */
    mem_heap_info = (dog_mem_heap_info_t *)_align_mem_heap_begin;

    return mem_heap_info->mem_heap_size;
}
